#include "luster_alloc.hh"

#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>

#include <execinfo.h>

#include "luster_config.hh"
#include "luster_flow.hh"
#include "luster_log.hh"

namespace luster {

#if luster_CFG(MEMCHECK)
enum {
	BLOCK_MAX = 1024 * 128,
	BT_MAX = 128,
};

struct MemoryBlock {
	void *ptr;
	uintptr_t len;

	void *bt_buf[BT_MAX];
	uintptr_t bt_n;
};

static MemoryBlock g_blocks[BLOCK_MAX];
static uintptr_t g_blocks_len = 0;
#endif

void *allocate(uintptr_t n) {
#if !luster_CFG(MEMCHECK)
	return malloc(n);
#else
	if (g_blocks_len >= BLOCK_MAX) {
		logfatal("MEMCHECK: too much allocate");
	}

	void *ptr = malloc(n);

	MemoryBlock *blk = g_blocks + g_blocks_len;
	g_blocks_len += 1;

	*blk = (MemoryBlock){.ptr = ptr, .len = n};
	blk->bt_n = backtrace(blk->bt_buf, BT_MAX);

	logprint(LOG_DEBUG, "allocate(% " PRIuPTR ") = %p, id: %" PRIuPTR, n,
		ptr, g_blocks_len - 1);
	return ptr;
#endif
}

void deallocate(void *p) {
#if !luster_CFG(MEMCHECK)
	return free(p);
#else
	for_range(0, g_blocks_len) {
		if (g_blocks[it].ptr == p) {
			logprint(LOG_DEBUG,
				"deallocate(%p), len: % " PRIuPTR
				", id: %" PRIuPTR,
				p, g_blocks[it].len, it);

			g_blocks[it].ptr = NULL;
			g_blocks[it].len = 0;
			free(p);
			return;
		}
	}

	logfatal("MEMCHECK: deallocate from other allocator: %p", p);
#endif
}

void *reallocate(void *p, uintptr_t n) {
#if !luster_CFG(MEMCHECK)
	return realloc(p, n);
#else
	if (p == NULL) {
		return allocate(n);
	}

	for_range(0, g_blocks_len) {
		if (g_blocks[it].ptr == p) {
			logprint(LOG_DEBUG, "realloc(%p, %" PRIuPTR ")", p, n);

			p = realloc(p, n);
			g_blocks[it].ptr = p;
			g_blocks[it].len = n;
			return p;
		}
	}

	logfatal("MEMCHECK: re-allocate for memory from other allocator, ptr: "
		 "%p",
		p);
#endif
}

void check_memleak(void) {
#if luster_CFG(MEMCHECK)
	int c = 0;

	for_range(0, g_blocks_len) {
		if (g_blocks[it].ptr != NULL) {
			c += 1;

			logprint(LOG_ERROR,
				"MEMCHECK: leak block (%p+%" PRIuPTR
				") #%" PRIuPTR,
				g_blocks[it].ptr, g_blocks[it].len, it);

			backtrace_symbols_fd(
				g_blocks[it].bt_buf, g_blocks[it].bt_n, 1);
		}
	}

	if (c > 0) {
		logfatal("MEMCHECK: leak %d blocks", c);
	}

	logprint(LOG_INFO, "MEMCHECK: ok");
#endif
}

} // namespace luster
